package top.lunarye.support.utils;

import cn.hutool.core.collection.CollUtil;
import com.google.common.base.Throwables;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import top.lunarye.common.constant.MessageConstant;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author lunar
 * @since 2022-07-12
 */
@Component
@Slf4j
public class RedisUtils {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 向redis中的有序集合中插入
     * @param setName 有序集合名称
     * @param value 插入的键值
     * @param score 键对应的分数
     * @return 插入的结果
     */
    public Boolean zSet(String setName, String value, Long score) {
        try {
            return stringRedisTemplate.opsForZSet().add(setName, value, score);
        } catch (Exception e) {
            log.error("RedisUtils#zSet fail! e:{}", Throwables.getStackTraceAsString(e));
            return false;
        }
    }

    /**
     * 移除有序集合中的元素
     * @param setName 有序集合名称
     * @param value 需要移除的值
     * @return 移除的结果
     */
    public Boolean zRem(String setName, String value) {
        try {
            Long result = stringRedisTemplate.opsForZSet().remove(setName, value);
            return result == 1;
        } catch (Exception e) {
            log.error("RedisUtils#zRem fail! e: {}", Throwables.getStackTraceAsString(e));
            return false;
        }
    }

    /**
     * 获取有序集合中指定分数范围内的元素
     * @param setName 集合名
     * @param score 分数上限
     * @return 范围内的元素集合
     */
    public Set<String> zRange(String setName, Long score) {
        try {
            return stringRedisTemplate.opsForZSet().range(setName, 0, score);
        } catch (Exception e) {
            log.error("RedisUtils#zRange fail! e:{}", Throwables.getStackTraceAsString(e));
            return null;
        }
    }

    /**
     * mGet将结果封装为Map
     *
     * @param keys
     */
    public Map<String, String> mGet(List<String> keys) {
        HashMap<String, String> result = new HashMap<>(keys.size());
        try {
            List<String> value = stringRedisTemplate.opsForValue().multiGet(keys);
            if (CollUtil.isNotEmpty(value)) {
                for (int i = 0; i < keys.size(); i++) {
                    result.put(keys.get(i), value.get(i));
                }
            }
        } catch (Exception e) {
            log.error("RedisUtils#mGet fail! e:{}", Throwables.getStackTraceAsString(e));
        }
        return result;
    }

    /**
     * pipeline 设置 key-value 并设置过期时间
     */
    public void pipelineSetEx(Map<String, String> keyValues, Long seconds) {
        try {
            stringRedisTemplate.executePipelined((RedisCallback<String>) connection -> {
                for (Map.Entry<String, String> entry : keyValues.entrySet()) {
                    connection.setEx(entry.getKey().getBytes(), seconds,
                            entry.getValue().getBytes());
                }
                return null;
            });
        } catch (Exception e) {
            log.error("RedisUtils#pipelineSetEx fail! e:{}", Throwables.getStackTraceAsString(e));
        }
    }

    /**
     * 执行指定的lua脚本返回执行结果
     * --KEYS[1]: 限流 key
     * --ARGV[1]: 限流窗口
     * --ARGV[2]: 当前时间戳（作为score）
     * --ARGV[3]: 阈值
     * --ARGV[4]: score 对应的唯一value
     *
     * @param redisScript
     * @param keys
     * @param args
     * @return
     */
    public Boolean execLimitLua(RedisScript<Long> redisScript, List<String> keys, String... args) {

        try {
            Long execute = stringRedisTemplate.execute(redisScript, keys, args);

            assert execute != null;
            return MessageConstant.TRUE.equals(execute.intValue());
        } catch (Exception e) {

            log.error("redis execLimitLua fail! e:{}", Throwables.getStackTraceAsString(e));
        }

        return false;
    }
}
